﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using TicTacGadget.Services;
using System.Windows.Media.Animation;
using System.Diagnostics;

namespace TicTacGadget
{
    public partial class GameBoard : UserControl
    {
        public Guid GameId { get; set; }
        public IGameHost Host { get; set; }
        public GameState State { get; private set; }
        public bool IsGameMaster { get; set; }
        int sequenceIndex = 0;
        
        int Size = 3;

        public GameBoard(IGameHost host, Guid gameId)
        {
            Host = host;
            GameId = gameId;

            InitializeComponent();

            InitializeGameBoard();
        }

        private void InitializeGameBoard()
        {
            for (int x = 0; x < Size; x++)
            {
                Board.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                Board.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
                for (int y = 0; y < Size; y++)
                {
                    Button b = new Button();
                    b.Row(x);
                    b.Column(y);
                    b.Click += ButtonClick;
                    b.BorderBrush = new SolidColorBrush(Colors.LightGray);
                    b.BorderThickness = new Thickness(1);
                    b.Background = new RadialGradientBrush(new GradientStopCollection
                        {
                            new GradientStop { Color = Colors.White, Offset = .3 },
                            new GradientStop { Color = Colors.Black, Offset = 3.3 }
                        });
                    b.FontFamily = new FontFamily("Comic, Arial, Sans-Serif");
                    b.Margin = new Thickness(1);

                    Board.Children.Insert(0, b);
                    Debug.WriteLine("Inserting button at " + x + "," + y);
                }
            }
            ResizeButtonFont();
            this.SizeChanged += delegate
            {
                ResizeButtonFont();
            };
        }

        private void ResizeButtonFont()
        {
            double newSize = ButtonFontSize();
            foreach (var b in Board.Children.OfType<Button>())
            {
                b.FontSize = newSize;
            }
        }

        void ButtonClick(object sender, RoutedEventArgs e)
        {
            var button = sender as Button;
            if (!button.IsEnabled || State == GameState.WaitingForOpponent)
                return;

            if (State == GameState.Idling)
            {
                IsGameMaster = true;
                ChangeState(GameState.WaitingForOpponent, "Waiting for another player to join the game");
            }
            else
            {
                ChangeState(GameState.WaitingForOpponent, "Waiting for your opponent's move");
            }

            var move = new Move
            {
                X = button.Column(),
                Y = button.Row(),
                Player = Host.Player,
                Host = Host.HostId,
                Sequence = sequenceIndex++
            };

            button.IsEnabled = false;
            button.Content = GetSymbolFor(move.Sequence);
            button.Tag = move;

            Debug.WriteLine("Setting " + move.X + "," + move.Y + " with symbol " + button.Content);
            Host.GameBoardMove(GameId, move);
            
            bool gameEnded = WasLastMove(move);
            if (!gameEnded)
                StartAnimating(button, ButtonFontSize() * 5);
        }

        public void ShowMove(Move move)
        {
            Button button = Button(move);
            bool previouslyDisplayed = button.Tag != null;
            if (!previouslyDisplayed)
            {
                sequenceIndex = 1 + move.Sequence;
            }

            button.IsEnabled = false;
            button.Content = GetSymbolFor(move.Sequence);
            button.Tag = move;

            Debug.WriteLine("Showing move " + button.Content + " at " + move.X + "," + move.Y + " with sequence " + move.Sequence + " from player " + move.Player);

            if (State == GameState.ObservingExternal)
            {
                // continue observing
            }
            else if (State == GameState.Idling || State == GameState.ConsideringToJoin)
            {
                if(move.Sequence == 0)
                    ChangeState(GameState.ConsideringToJoin, "Place your O to join the game");
                else
                    ChangeToObserving();
            }
            else if (State != GameState.ObservingExternal)
                ChangeState(GameState.ThinkingBeforeMove, "Your turn to place an " + GetSymbolFor(move.Sequence + 1));

            bool gameEnded = WasLastMove(move);
            if (!previouslyDisplayed && !gameEnded && move.Host != Host.HostId)
                StartAnimating(button, 0);
        }

        private void ChangeToObserving()
        {
            string[] players = AllMoves().Select(m => m.Player).Distinct().ToArray();
            ChangeState(GameState.ObservingExternal, string.Join(" vs. ", players));
        }

        private void StartAnimating(Button button, double fromSize)
        {
            Storyboard sb = new Storyboard();
            sb.Children.Add(ResizeAnimation(button, fromSize));
            sb.Children.Add(RevealAnimation(button));
            sb.Completed += delegate { ResizeButtonFont(); };
            sb.Begin();
        }

        private DoubleAnimation ResizeAnimation(Button button, double fromSize)
        {
            var resizeAnimation = new DoubleAnimation
            {
                From = fromSize,
                To = button.FontSize,
                Duration = TimeSpan.FromSeconds(.25)
            };
            Storyboard.SetTargetProperty(resizeAnimation, new PropertyPath("FontSize"));
            Storyboard.SetTarget(resizeAnimation, button);
            return resizeAnimation;
        }

        private ColorAnimation RevealAnimation(Button button)
        {
            ColorAnimation revealAnimation = new ColorAnimation
            {
                From = Colors.Transparent,
                To = ((SolidColorBrush)button.Foreground).Color,
                Duration = TimeSpan.FromSeconds(.25)
            };
            Storyboard.SetTarget(revealAnimation, button);
            Storyboard.SetTargetProperty(revealAnimation, new PropertyPath("(TextBlock.Foreground).(SolidColorBrush.Color)"));
            return revealAnimation;
        }

        private bool WasLastMove(Move lastMove)
        {
            int winningRowLength = 3;
            var moves = AllMoves().ToList();
            if(moves.Count >= 9)
            {
                ChangeText("Draw!");
                Host.GameBoardEnd(lastMove, AllMoves().ToList());
                return true;
            }

            foreach (var move in moves)
            {
                for (int dx = -1; dx <= 1; dx++)
                {
                    for (int dy = -1; dy <= 1; dy++)
                    {
                        if (IsWinningMove(move, dx, dy, winningRowLength - 1))
                        {
                            Host.GameBoardEnd(lastMove, AllMoves().ToList());
                            RevealWinner(lastMove);
                            RevealWinningMoves(move, dx, dy);
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private void RevealWinner(Move winningMove)
        {
            if(State == GameState.ObservingExternal)
            {
                State = GameState.Ended;
            }
            else if (winningMove.Host == Host.HostId)
            {
                State = GameState.Won;
            }
            else
            {
                State = GameState.Lost;
            }

            Message.Foreground = new SolidColorBrush(GameColor());
            ChangeText(winningMove.Player + " wins!");
        }

        private void RevealWinningMoves(Move move, int dx, int dy)
        {
            Button b = Button(move);
            b.BorderBrush = new SolidColorBrush(GameColor());
            var nextMove = GetNextMove(move, dx, dy);
            if (nextMove != null)
            {
                RevealWinningMoves(nextMove, dx, dy);
            }
        }

        public Color GameColor()
        {
            switch (State)
            {
                case GameState.Ended:
                    return Colors.DarkGray;
                case GameState.Won:
                    return Colors.Green;
                case GameState.Lost:
                    return Colors.Red;
            }
            return Color.FromArgb(255, 0, 0, 0);
        }

        private bool IsWinningMove(Move move, int dx, int dy, int winningRowLength)
        {
            if (dx == 0 && dy == 0)
                return false;
            if (winningRowLength == 0)
                return true;
            var nextMove = GetNextMove(move, dx, dy);
            if (nextMove == null)
                return false;
            if (nextMove.Host != move.Host)
                return false;
            return IsWinningMove(nextMove, dx, dy, winningRowLength - 1);
        }

        private Button Button(Move move)
        {
            return Buttons().FirstOrDefault(b => b.Column() == move.X && b.Row() == move.Y);
        }

        private IEnumerable<Button> Buttons()
        {
            return Board.Children.OfType<Button>();
        }

        private void ChangeState(GameState newState, string message)
        {
            Debug.WriteLine("Changing state to " + newState + " with message: " + message);

            IsEnabled = newState == GameState.Idling || newState == GameState.ConsideringToJoin || newState == GameState.ThinkingBeforeMove;
            State = newState;
            ChangeText(message);
        }

        private void ChangeText(string message)
        {
            Message.Text = message;
        }

        private object GetSymbolFor(int sequenceIndex)
        {
            bool isFromHost = sequenceIndex % 2 == 0;
            return isFromHost ? "X" : "O";
        }

        private Move GetNextMove(Move move, int dx, int dy)
        {
            return AllMoves().FirstOrDefault(m => m.X == move.X + dx && m.Y == move.Y + dy);
        }

        internal IEnumerable<Move> AllMoves()
        {
            return Buttons()
                .Select(b => b.Tag).OfType<Move>()
                .OrderBy(b => b.Sequence);
        }

        public void ShowGame(List<Move> moves)
        {
            foreach (var m in moves)
            {
                ShowMove(m);
            }
            if (moves.Count > 1)
                ChangeToObserving();
        }

        private double ButtonFontSize()
        {
            return ActualHeight / Size / 2;
        }

        internal bool IsPlaying()
        {
            return State == GameState.ThinkingBeforeMove
                || State == GameState.WaitingForOpponent;
        }

        internal bool IsEnded()
        {
            return State == GameState.Ended
                || State == GameState.Won
                || State == GameState.Lost;
        }
    }
}
